#import "draw.h"

@implementation SyncScrollView
/*
 * This subclass of ScrollView is extremely useful for programmers
 * who want some View to scroll along with a main docView.  A good
 * example is a spreadsheet that wants its column and row headings
 * to scroll along with the cells in the spreadsheet itself.
 * It is actually quite simple.  We simply override tile to place
 * two ClipViews with our views (rulers in this case) in them into
 * the view hierarchy, then override scrollClip:to: to update their
 * drawing origins when the docView is scrolled.  We also override
 * reflectScroll: since we don't want that to apply to our little
 * ruler views, only to the main docView.
 */

- setRulerClass:factoryId
{
    if ([factoryId conformsTo:@protocol(Ruler)]) rulerClass = factoryId;
    return self;
}

- setRulerWidths:(NXCoord)horizontal :(NXCoord)vertical
{
    horizontalRulerWidth = horizontal;
    verticalRulerWidth = vertical;
    return self;
}

- (BOOL)bothRulersAreVisible
{
    return verticalRulerIsVisible && horizontalRulerIsVisible;
}

- (BOOL)eitherRulerIsVisible
{
    return verticalRulerIsVisible || horizontalRulerIsVisible;
}

- (BOOL)verticalRulerIsVisible
{
    return verticalRulerIsVisible;
}

- (BOOL)horizontalRulerIsVisible
{
    return horizontalRulerIsVisible;
}

- setRulerOrigin:(RulerOrigin)origin
{
    RulerOrigin oldRulerOrigin = rulerOrigin;

    rulerOrigin = origin;
    switch (origin) {
	case LowerRight:
	    [[hClipRuler docView] setFlipped:YES];
	    break;
	case UpperRight:
	    [[hClipRuler docView] setFlipped:YES];
	case UpperLeft:
	    [[vClipRuler docView] setFlipped:YES];
	case LowerLeft:
	    break;
	default:
	    rulerOrigin = oldRulerOrigin;
	    break;
    }

    return self;
}

- makeRulers
/*
 * This makes the rulers.
 * We do this lazily in case the user never asks for the rulers.
 */
{
    View <Ruler> *ruler;
    NXRect aRect, bRect;

    if (!rulerClass || (!horizontalRulerWidth && !verticalRulerWidth)) return nil;

    if (horizontalRulerWidth) {
	[[contentView docView] getFrame:&aRect];
	NXDivideRect(&aRect, &bRect, horizontalRulerWidth, NX_YMIN);
	hClipRuler = [[ClipView allocFromZone:[self zone]] init];
	ruler = [[rulerClass allocFromZone:[self zone]] initFrame:&bRect];
	[hClipRuler setDocView:ruler];
    }
    if (verticalRulerWidth) {
	[[contentView docView] getFrame:&aRect];
	NXDivideRect(&aRect, &bRect, verticalRulerWidth, NX_XMIN);
	vClipRuler = [[ClipView allocFromZone:[self zone]] init];
	ruler = [[rulerClass allocFromZone:[self zone]] initFrame:&bRect];
	[vClipRuler setDocView:ruler];
    }
    [self setRulerOrigin:rulerOrigin];
    rulersMade = 1;

    return self;
}

- updateRulers:(const NXRect *)rect
{
    if (!rect) {
	if (verticalRulerIsVisible) {
	    [[vClipRuler docView] hidePosition];
	}
	if (horizontalRulerIsVisible) {
	    [[hClipRuler docView] hidePosition];
	}
    } else {
	if (verticalRulerIsVisible) {
	    [[vClipRuler docView] showPosition:rect->origin.y :rect->origin.y + rect->size.height];
	}
	if (horizontalRulerIsVisible) {
	    [[hClipRuler docView] showPosition:rect->origin.x :rect->origin.x + rect->size.width];
	}
    }

    return self;
}

- updateRuler
{
    NXRect aRect, bRect;

    if (horizontalRulerIsVisible) {
	[[contentView docView] getFrame:&aRect];
	NXDivideRect(&aRect, &bRect, horizontalRulerWidth, NX_YMIN);
	bRect.size.width += verticalRulerWidth;
	[[hClipRuler docView] setFrame:&bRect];
	[hClipRuler display];
    }
    if (verticalRulerIsVisible) {
	[[contentView docView] getFrame:&aRect];
	NXDivideRect(&aRect, &bRect, verticalRulerWidth, NX_XMIN);
	[[vClipRuler docView] setFrame:&bRect];
	[vClipRuler display];
    }

    return self;
}

- (BOOL)showRuler:(BOOL)showIt isHorizontal:(BOOL)isHorizontal
/*
 * Adds or removes a ruler from the view hierarchy.
 * Returns whether or not it succeeded in doing so.
 */
{
    ClipView *ruler;
    BOOL isVisible;
    NXRect cRect, rRect;

    isVisible = isHorizontal ? horizontalRulerIsVisible : verticalRulerIsVisible;
    if ((showIt && isVisible) || (!showIt && !isVisible)) return NO;
    if (showIt && !rulersMade && ![self makeRulers]) return NO;
    ruler = isHorizontal ? hClipRuler : vClipRuler;

    if (!showIt && isVisible) {
	[ruler removeFromSuperview];
	if (isHorizontal) {
	    horizontalRulerIsVisible = NO;
	} else {
	    verticalRulerIsVisible = NO;
	}
    } else if (showIt && !isVisible && ruler) {
	[self addSubview:ruler];
	[window disableDisplay];
	[contentView getBounds:&cRect];
	[hClipRuler getBounds:&rRect];
	[hClipRuler setDrawOrigin:cRect.origin.x :rRect.origin.y];
	[vClipRuler getBounds:&rRect];
	[vClipRuler setDrawOrigin:rRect.origin.x :cRect.origin.y];
	[window reenableDisplay];
	if (isHorizontal) {
	    horizontalRulerIsVisible = YES;
	} else {
	    verticalRulerIsVisible = YES;
	}
    }

    return YES;
}

- adjustSizes
{
    id windelegate;
    NXRect winFrame;

    windelegate = [window delegate];
    if ([windelegate respondsTo:@selector(windowWillResize:toSize:)]) {
	[window getFrame:&winFrame];
	[windelegate windowWillResize:window toSize:&winFrame.size];
	[window placeWindow:&winFrame];
    }
    [self resizeSubviews:(NXSize *)nil];

    return self;
}

- showHorizontalRuler:(BOOL)flag
{
    if ([self showRuler:flag isHorizontal:YES]) [self adjustSizes];
    return self;
}

- showVerticalRuler:(BOOL)flag
{
    if ([self showRuler:flag isHorizontal:NO]) [self adjustSizes];
    return self;
}

- showHideRulers:sender
/*
 * If both rulers are visible, they are both hidden.
 * Otherwise, both rulers are made visible.
 */
{
    BOOL resize = NO;

    if (verticalRulerIsVisible && horizontalRulerIsVisible) {
	resize = [self showRuler:NO isHorizontal:YES];
	resize = [self showRuler:NO isHorizontal:NO] || resize;
    } else {
	if (!horizontalRulerIsVisible) resize = [self showRuler:YES isHorizontal:YES];
	if (!verticalRulerIsVisible) resize = [self showRuler:YES isHorizontal:NO] || resize;
    }
    if (resize) [self adjustSizes];

    return self;
}

/* ScrollView-specific stuff */

- free
{
    if (!horizontalRulerIsVisible) [hClipRuler free];
    if (!verticalRulerIsVisible) [vClipRuler free];
    return [super free];    
}

- reflectScroll:cView
/*
 * We only reflect scroll in the contentView, not the rulers.
 */
{
    if (cView == hClipRuler || cView == vClipRuler) return self;
    return [super reflectScroll:cView];
}

- tile
/*
 * Here is where we lay out the subviews of the ScrollView.
 * Note the use of NXDivideRect() to "slice off" a section of
 * a rectangle.  This is useful since the two scrollers each
 * result in slicing a section off the contentView of the
 * ScrollView.
 */
{
    NXRect aRect, bRect, cRect;

    [super tile];

    if (horizontalRulerIsVisible || verticalRulerIsVisible) {
	[contentView getFrame:&aRect];
	[[self docView] getFrame:&cRect];
	if (horizontalRulerIsVisible && hClipRuler) {
	    NXDivideRect(&aRect, &bRect, horizontalRulerWidth, NX_YMIN);
	    [hClipRuler setFrame:&bRect];
	    [[hClipRuler docView] sizeTo:cRect.size.width+verticalRulerWidth :bRect.size.height];
	}
	if (verticalRulerIsVisible && vClipRuler) {
	    NXDivideRect(&aRect, &bRect, verticalRulerWidth, NX_XMIN);
	    [vClipRuler setFrame:&bRect];
	    [[vClipRuler docView] sizeTo:bRect.size.width :cRect.size.height];
	}
	[contentView setFrame:&aRect];
    }

    return self;
}

- scrollClip:(ClipView *)aClipView to:(const NXPoint *)aPoint
/*
 * This is sent to us instead of rawScroll:.
 * We scroll the two rulers, then the clipView itself.
 */
{
    id fr;
    NXRect rRect;

    if (horizontalRulerIsVisible && hClipRuler) {
	[hClipRuler getBounds:&rRect];
	rRect.origin.x = aPoint->x;
	[hClipRuler rawScroll:&(rRect.origin)];
    }
    if (verticalRulerIsVisible && vClipRuler) {
	[vClipRuler getBounds:&rRect];
	rRect.origin.y = aPoint->y;
	[vClipRuler rawScroll:&(rRect.origin)];
    }

    [aClipView rawScroll:aPoint];

    fr = [window firstResponder];
    if ([fr respondsTo:@selector(isRulerVisible)] && [fr isRulerVisible]) [fr updateRuler]; // keeps Text ruler up-to-date

    return self;
}

- descendantFrameChanged:sender
/*
 * Any time the docView is resized, this method is
 * called to update the size of the rulers to be equal to
 * the size of the docView.
 */
{
    NXRect aRect, bRect, cRect;

    [super descendantFrameChanged:sender];
    if (horizontalRulerIsVisible || verticalRulerIsVisible) {
	[contentView getFrame:&aRect];
	[[self docView] getFrame:&cRect];
	if (horizontalRulerIsVisible && hClipRuler) {
	    NXDivideRect(&aRect, &bRect, horizontalRulerWidth, NX_YMIN);
	    [[hClipRuler docView] sizeTo:cRect.size.width+verticalRulerWidth :bRect.size.height];
	}
	if (verticalRulerIsVisible && vClipRuler) {
	    NXDivideRect(&aRect, &bRect, verticalRulerWidth, NX_XMIN);
	    [[vClipRuler docView] sizeTo:bRect.size.width :cRect.size.height];
	}
    }

    return self;
}

@end
